package org.springframework.security.config.annotation.web.configurers;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.authentication.*;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.matcher.*;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;

import java.util.Arrays;
import java.util.Collections;

/**
 * @author Dillon
 * @date 2024/7/9
 * @slogan 致敬大师 致敬未来的你
 * @desc 抽象的身份认证配置类，用于执行认证
 */
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter>
		extends AbstractHttpConfigurer<T, B> {

	/**
	 * 设置的登录过滤器
	 */
	private F authFilter;

	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;

	private SavedRequestAwareAuthenticationSuccessHandler defaultSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();

	private AuthenticationSuccessHandler successHandler = this.defaultSuccessHandler;

	/**
	 * 默认的访问受保护资源时，将路径重定向到登录也出入口实现类
	 */
	private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;

	private boolean customLoginPage;

	private String loginPage;

	private String loginProcessingUrl;

	private AuthenticationFailureHandler failureHandler;

	private boolean permitAll;

	private String failureUrl;

	/**
	 * 构造函数，
	 * 默认设置登录页面为 login
	 */
	protected AbstractAuthenticationFilterConfigurer() {
		setLoginPage("/login");
	}

	/**
	 * 构造函数
	 * 设置认证过滤器，默认的登录url
	 * @param authenticationFilter 认证过滤器
	 * @param defaultLoginProcessingUrl 默认url
	 */
	protected AbstractAuthenticationFilterConfigurer(F authenticationFilter, String defaultLoginProcessingUrl) {
		this();
		this.authFilter = authenticationFilter;
		// 只有设置url 才会设置默认url
		if (defaultLoginProcessingUrl != null) {
			loginProcessingUrl(defaultLoginProcessingUrl);
		}
	}

	/**
	 * 设置默认的登录成功跳转url
	 * @param defaultSuccessUrl 默认登录成功url
	 * @return AbstractHttpConfigurer
	 */
	public final T defaultSuccessUrl(String defaultSuccessUrl) {
		return defaultSuccessUrl(defaultSuccessUrl, false);
	}

	/**
	 * 设置默认的登录成功跳转url
	 * @param defaultSuccessUrl 默认登录成功url
	 * @param alwaysUse 登录成功后是否忽略登录前请求，直接跳转到设置url
	 * @return AbstractHttpConfigurer
	 */
	public final T defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse) {
		SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
		handler.setDefaultTargetUrl(defaultSuccessUrl);
		handler.setAlwaysUseDefaultTargetUrl(alwaysUse);
		this.defaultSuccessHandler = handler;
		return successHandler(handler);
	}

	/**
	 * 设置登录处理url
	 * 同时给过滤器设置登录url
	 * @param loginProcessingUrl 默认的登录url
	 * @return AbstractHttpConfigurer
	 */
	public T loginProcessingUrl(String loginProcessingUrl) {
		this.loginProcessingUrl = loginProcessingUrl;
		this.authFilter.setRequiresAuthenticationRequestMatcher(createLoginProcessingUrlMatcher(loginProcessingUrl));
		return getSelf();
	}

	public T securityContextRepository(SecurityContextRepository securityContextRepository) {
		this.authFilter.setSecurityContextRepository(securityContextRepository);
		return getSelf();
	}

	/**
	 * 设置处理登录URL
	 * @param loginProcessingUrl 登录URL
	 * @return 请求匹配器
	 */
	protected abstract RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl);

	/**
	 * Specifies a custom {@link AuthenticationDetailsSource}. The default is
	 * {@link WebAuthenticationDetailsSource}.
	 * @param authenticationDetailsSource the custom {@link AuthenticationDetailsSource}
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T authenticationDetailsSource(
			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
		this.authenticationDetailsSource = authenticationDetailsSource;
		return getSelf();
	}

	/**
	 * Specifies the {@link AuthenticationSuccessHandler} to be used. The default is
	 * {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properties
	 * set.
	 * @param successHandler the {@link AuthenticationSuccessHandler}.
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T successHandler(AuthenticationSuccessHandler successHandler) {
		this.successHandler = successHandler;
		return getSelf();
	}

	/**
	 * Equivalent of invoking permitAll(true)
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T permitAll() {
		return permitAll(true);
	}

	/**
	 * Ensures the urls for {@link #failureUrl(String)} as well as for the
	 * {@link HttpSecurityBuilder}, the {@link #getLoginPage} and
	 * {@link #getLoginProcessingUrl} are granted access to any user.
	 * @param permitAll true to grant access to the URLs false to skip this step
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T permitAll(boolean permitAll) {
		this.permitAll = permitAll;
		return getSelf();
	}

	/**
	 * The URL to send users if authentication fails. This is a shortcut for invoking
	 * {@link #failureHandler(AuthenticationFailureHandler)}. The default is
	 * "/login?error".
	 * @param authenticationFailureUrl the URL to send users if authentication fails (i.e.
	 * "/login?error").
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T failureUrl(String authenticationFailureUrl) {
		T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl));
		this.failureUrl = authenticationFailureUrl;
		return result;
	}

	/**
	 * Specifies the {@link AuthenticationFailureHandler} to use when authentication
	 * fails. The default is redirecting to "/login?error" using
	 * {@link SimpleUrlAuthenticationFailureHandler}
	 * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} to use
	 * when authentication fails.
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T failureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
		this.failureUrl = null;
		this.failureHandler = authenticationFailureHandler;
		return getSelf();
	}

	/**
	 * 初始化构建对象
	 * 该方法只能时创建和修改共享状态，不能设置构建都西昂上的属性
	 *
	 * @param http 构建对象
	 * @throws Exception 执行异常
	 */
	@Override
	public void init(B http) throws Exception {
		updateAuthenticationDefaults();
		updateAccessDefaults(http);
		registerDefaultAuthenticationEntryPoint(http);
	}

	/**
	 * 注册默认的访问受保护资源时的 跳转入口点实现类
	 * @param http 构建对象
	 */
	@SuppressWarnings("unchecked")
	protected final void registerDefaultAuthenticationEntryPoint(B http) {
		registerAuthenticationEntryPoint(http, this.authenticationEntryPoint);
	}

	@SuppressWarnings("unchecked")
	protected final void registerAuthenticationEntryPoint(B http, AuthenticationEntryPoint authenticationEntryPoint) {
		ExceptionHandlingConfigurer<B> exceptionHandling = http.getConfigurer(ExceptionHandlingConfigurer.class);
		if (exceptionHandling == null) {
			return;
		}
		exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint),
				getAuthenticationEntryPointMatcher(http));
	}

	protected final RequestMatcher getAuthenticationEntryPointMatcher(B http) {
		ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
		if (contentNegotiationStrategy == null) {
			contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
		}
		MediaTypeRequestMatcher mediaMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
				MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"), MediaType.TEXT_HTML,
				MediaType.TEXT_PLAIN);
		mediaMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
		RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
				new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
		return new AndRequestMatcher(Arrays.asList(notXRequestedWith, mediaMatcher));
	}

	/**
	 * 配置构建对象 设置必要的属性等
	 *
	 * @param http 构建对象
	 * @throws Exception 执行异常
	 */
	@Override
	public void configure(B http) throws Exception {
		// 访问受保护资源跳转 设置portMapper映射类
		PortMapper portMapper = http.getSharedObject(PortMapper.class);
		if (portMapper != null) {
			this.authenticationEntryPoint.setPortMapper(portMapper);
		}
		// 登录成功处理器请求缓存类设置
		RequestCache requestCache = http.getSharedObject(RequestCache.class);
		if (requestCache != null) {
			this.defaultSuccessHandler.setRequestCache(requestCache);
		}
		// 登录过滤器域设置
		this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
		this.authFilter.setAuthenticationSuccessHandler(this.successHandler);
		this.authFilter.setAuthenticationFailureHandler(this.failureHandler);
		if (this.authenticationDetailsSource != null) {
			this.authFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
		}
		// 设置session认证策略
		SessionAuthenticationStrategy sessionAuthenticationStrategy = http
			.getSharedObject(SessionAuthenticationStrategy.class);
		if (sessionAuthenticationStrategy != null) {
			this.authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
		}
		// 设置记住我
		RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
		if (rememberMeServices != null) {
			this.authFilter.setRememberMeServices(rememberMeServices);
		}
		SecurityContextConfigurer securityContextConfigurer = http.getConfigurer(SecurityContextConfigurer.class);
		if (securityContextConfigurer != null && securityContextConfigurer.isRequireExplicitSave()) {
			SecurityContextRepository securityContextRepository = securityContextConfigurer
				.getSecurityContextRepository();
			this.authFilter.setSecurityContextRepository(securityContextRepository);
		}
		// 设置登录缓存策略
		this.authFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
		// 调用后置处理器
		F filter = postProcess(this.authFilter);
		http.addFilter(filter);
	}

	/**
	 * 设置默认登录页面路径
	 * @param loginPage 登录页面路径
	 * @return
	 */
	protected T loginPage(String loginPage) {
		setLoginPage(loginPage);
		updateAuthenticationDefaults();
		this.customLoginPage = true;
		return getSelf();
	}

	/**
	 * @return true if a custom login page has been specified, else false
	 */
	public final boolean isCustomLoginPage() {
		return this.customLoginPage;
	}

	/**
	 * Gets the Authentication Filter
	 * @return the Authentication Filter
	 */
	protected final F getAuthenticationFilter() {
		return this.authFilter;
	}

	/**
	 * Sets the Authentication Filter
	 * @param authFilter the Authentication Filter
	 */
	protected final void setAuthenticationFilter(F authFilter) {
		this.authFilter = authFilter;
	}

	/**
	 * Gets the login page
	 * @return the login page
	 */
	protected final String getLoginPage() {
		return this.loginPage;
	}

	/**
	 * Gets the Authentication Entry Point
	 * @return the Authentication Entry Point
	 */
	protected final AuthenticationEntryPoint getAuthenticationEntryPoint() {
		return this.authenticationEntryPoint;
	}

	/**
	 * Gets the URL to submit an authentication request to (i.e. where username/password
	 * must be submitted)
	 * @return the URL to submit an authentication request to
	 */
	protected final String getLoginProcessingUrl() {
		return this.loginProcessingUrl;
	}

	/**
	 * Gets the URL to send users to if authentication fails
	 * @return the URL to send users if authentication fails (e.g. "/login?error").
	 */
	protected final String getFailureUrl() {
		return this.failureUrl;
	}

	/**
	 * 构建默认的配置
	 */
	protected final void updateAuthenticationDefaults() {
		// 如果没有配置登录认证url 则使用默认登录页面url作为登录url
		if (this.loginProcessingUrl == null) {
			loginProcessingUrl(this.loginPage);
		}
		// 如果未配置失败处理器，则配置登录失败url
		if (this.failureHandler == null) {
			failureUrl(this.loginPage + "?error");
		}
		// 默认配置登出 配置类的登出路径
		LogoutConfigurer<B> logoutConfigurer = getBuilder().getConfigurer(LogoutConfigurer.class);
		if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
			logoutConfigurer.logoutSuccessUrl(this.loginPage + "?logout");
		}
	}

	/**
	 * 如果配置了通过全部
	 * 则配置 登录页面 登录url 登录失败url 不进行拦截
	 * @param http httpSecurity
	 */
	protected final void updateAccessDefaults(B http) {
		if (this.permitAll) {
			PermitAllSupport.permitAll(http, this.loginPage, this.loginProcessingUrl, this.failureUrl);
		}
	}

	/**
	 * 设置登录页面
	 * 设置访问受保护资源时重定向登录页面入口点
	 * @param loginPage 登录页面路径
	 */
	private void setLoginPage(String loginPage) {
		this.loginPage = loginPage;
		this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage);
	}

	/**
	 * 返回当前对象
	 * @return 当前对象
	 */
	@SuppressWarnings("unchecked")
	private T getSelf() {
		return (T) this;
	}

}
